package com.rfb;

import java.io.IOException;

import com.util.DESUtil;

import com.exception.ProtocolNotSupportedException;
import com.util.*;

public class VncClient {
	private RfbProtocol rfb;
	private byte securityType;
	private Framebuffer framebuffer;
	private EncodeFactory factory;

	private Thread backgroundThread;

	public VncClient() {
		rfb = new RfbProtocol();
		securityType = 0;
	}

	public boolean Connect(String host, int port) {
		boolean bRet = rfb.Connect(host, port);
		// boolean bRet = rfb.Connect("ivy-pc", 5900);
		if (bRet) {
			try {
				rfb.ReadProtocolVersion();
			} catch (ProtocolNotSupportedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			rfb.WriteProtocolVersion();

			byte[] types = rfb.ReadSecurityTypes();
			if (types.length > 0) {
				if (types[0] == 0) {
					System.out.println("Connection failed");
					return false;
				} else {
					securityType = rfb.GetSupportedSecurityType(types);
					rfb.WriteSecurityType(securityType);

					if (rfb.ServerVersion() == 3.8f && securityType == 1) {
						System.out.println("Connection failed"
								+ rfb.ReadSecurityFailureReason());
					}
					return (securityType > 1) ? true : false;
				}
			} else {
				return false;
			}
		} else {
			return false;
		}
	}

	public boolean Authenticate(String passwd) {
		if (passwd == null) {
			return false;
		}

		if (securityType == 2) {
			byte[] challenge = rfb.ReadSecurityChallenge();
			rfb.WriteSecurityResponse(EncChallenge(passwd, challenge));
		} else {
			return false;
		}

		if (rfb.ReadSecurityResult() == 0) {
			return true;
		} else {
			if (rfb.ServerVersion() == 3.8f) {
				rfb.ReadSecurityFailureReason();
			}
			return false;
		}
	}

	public void Disconnect() {
		if (rfb != null) {
			rfb.Close();
		}
	}

	/*
	 * Wait to be implemented
	 */
	public byte[] EncChallenge(String passwd, byte[] challenge) {
		DESUtil des = new DESUtil(passwd);
		return des.EncryptByte(challenge);
	}

	public void Initialize() {
		try {
			rfb.WriteClientInitialisation(false);
			framebuffer = rfb.ReadServerInitialization();
			rfb.WriteSetPixelFormat(framebuffer);
			rfb.WriteSetEncodings();
			factory = new EncodeFactory(rfb, framebuffer);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public void RequestScreenUpdate() {
		try {
			int w = framebuffer.getWidth();
			int h = framebuffer.getHeight();
			rfb.WriteFramebufferUpdateRequest(false, 0, 0, w, h);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public void GetRfbUpdate() {
		try {
			int messageType = rfb.ReadServerMessageType();
			switch (messageType) {
			case RfbProtocol.BELL: {
			}
				break;

			case RfbProtocol.FRAMEBUFFER_UPDATE: {
				int numRect = rfb.ReadFramebufferUpdate();
				for (int i = 0; i < numRect; i++) {
					VncRect vncRect = rfb.ReadVncRect();
					switch (vncRect.getEncodingType()) {
					case RfbProtocol.RAW_ENCODING: {
						DecodeRaw(vncRect);
					}
						break;

					case RfbProtocol.COPYRECT_ENCODING: {

					}
						break;

					case RfbProtocol.RRE_ENCODING: {

					}
						break;

					case RfbProtocol.CORRE_ENCODING: {

					}
						break;

					case RfbProtocol.HEXTILE_ENCODING: {

					}
						break;

					case RfbProtocol.ZRLE_ENCODING: {

					}
						break;

					default:
						break;
					}
				}
			}
				break;

			case RfbProtocol.SERVER_CUT_TEXT: {

			}
				break;

			case RfbProtocol.SET_COLOUR_MAP_ENTRIES: {

			}
				break;

			default:
				break;
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	private void DecodeRaw(VncRect rect) {
		try {
			int count = rect.getWidth() * rect.getHeight();
			for (int i = 0; i < count; i++) {
				byte[] b = rfb.ReadBytes(framebuffer.getBitsPerPixel()/8);
				int pixelData = ReadPixel(b);
				framebuffer.setPixels(i, pixelData);
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	private int ReadPixel(byte[] b){
		int iPixel = (int)(b[0] & 0xFF| 
                b[1] << 8|
                b[2] << 16| 
                b[3] << 24);
		byte red = (byte)((iPixel>>framebuffer.getRedShift()) & framebuffer.getRedMax());
		byte green = (byte)((iPixel>>framebuffer.getGreenShift()) & framebuffer.getGreenMax());
		byte blue = (byte)((iPixel>>framebuffer.getBlueShift()) & framebuffer.getBlueMax());
		
		return Rgb2ColorInt(red, green, blue);
	}
	
	private int Rgb2ColorInt(byte red, byte green, byte blue){
		return (int) (blue & 0xFF | green << 8 | red << 16 | 0xFF << 24);
	}
}
